package org.javahispano.javacup.tacticas.tacticas_aceptadas.Tactica1A;

import java.awt.Color;
import java.util.ArrayList;
import org.javahispano.javacup.modelo.*;
import java.util.List;
import java.util.Random;


public class Tactica1A implements Tactica {

    Posicion alineacion1[]=new Posicion[]{
        new Posicion(0,-52.41044776119403),
        new Posicion(-19.46564885496183,-31.6044776119403),
        new Posicion(0.2595419847328244,-31.082089552238806),
        new Posicion(19.984732824427482,-31.6044776119403),
        new Posicion(7.526717557251908,-11.753731343283583),
        new Posicion(-8.564885496183205,-11.753731343283583),
        new Posicion(-24.65648854961832,-2.3507462686567164),
        new Posicion(23.099236641221374,-2.873134328358209),
        new Posicion(-14.274809160305344,30.559701492537314),
        new Posicion(-0.7786259541984732,8.097014925373134),
        new Posicion(12.717557251908397,29.51492537313433)
    };

    Position align1[] = new Position[]{
        new Position(0,-52.41044776119403),
        new Position(-19.46564885496183,-31.6044776119403),
        new Position(0.2595419847328244,-31.082089552238806),
        new Position(19.984732824427482,-31.6044776119403),
        new Position(7.526717557251908,-11.753731343283583),
        new Position(-8.564885496183205,-11.753731343283583),
        new Position(-24.65648854961832,-2.3507462686567164),
        new Position(23.099236641221374,-2.873134328358209),
        new Position(-14.274809160305344,30.559701492537314),
        new Position(-0.7786259541984732,8.097014925373134),
        new Position(12.717557251908397,29.51492537313433)
    };

    double minX[] = new double[]{
        0,
        -Constantes.ANCHO_CAMPO_JUEGO/2,
        -Constantes.ANCHO_CAMPO_JUEGO/4,
        -Constantes.ANCHO_CAMPO_JUEGO/6,
        0,
        -Constantes.ANCHO_CAMPO_JUEGO/2,
        -Constantes.ANCHO_CAMPO_JUEGO/4,
        0,
        -Constantes.ANCHO_CAMPO_JUEGO/2,
        -Constantes.ANCHO_CAMPO_JUEGO/4,
        0
    };

    double maxX[] = new double[]{
        0,
        0,
        Constantes.ANCHO_CAMPO_JUEGO/6,
        Constantes.ANCHO_CAMPO_JUEGO/4,
        Constantes.ANCHO_CAMPO_JUEGO/2,
        0,
        Constantes.ANCHO_CAMPO_JUEGO/4,
        Constantes.ANCHO_CAMPO_JUEGO/2,
        0,
        Constantes.ANCHO_CAMPO_JUEGO/4,
        Constantes.ANCHO_CAMPO_JUEGO/2
    };

    Posicion alineacion2[]=new Posicion[]{
        new Posicion(0.2595419847328244,-52.41044776119403),
        new Posicion(-11.16030534351145,-31.082089552238806),
        new Posicion(11.16030534351145,-31.6044776119403),
        new Posicion(27.251908396946565,-27.94776119402985),
        new Posicion(-29.84732824427481,-26.902985074626866),
        new Posicion(8.564885496183205,-7.574626865671642),
        new Posicion(-10.641221374045802,-7.052238805970149),
        new Posicion(27.251908396946565,4.440298507462686),
        new Posicion(-29.32824427480916,3.3955223880597014),
        new Posicion(-0.2595419847328244,19.067164179104477),
        new Posicion(-0.2595419847328244,35.78358208955224)
    };

    Posicion alineacion3[]=new Posicion[]{
        new Posicion(0.2595419847328244,-52.41044776119403),
        new Posicion(-11.16030534351145,-31.082089552238806),
        new Posicion(11.16030534351145,-31.6044776119403),
        new Posicion(26.732824427480914,-20.111940298507463),
        new Posicion(-29.32824427480916,-21.67910447761194),
        new Posicion(0.2595419847328244,-0.26119402985074625),
        new Posicion(-18.946564885496183,-0.26119402985074625),
        new Posicion(18.946564885496183,-0.26119402985074625),
        new Posicion(-19.46564885496183,35.78358208955224),
        new Posicion(-0.2595419847328244,19.067164179104477),
        new Posicion(18.946564885496183,35.26119402985075)
    };

    Posicion alineacion4[]=new Posicion[]{
        new Posicion(0.2595419847328244,-52.41044776119403),
        new Posicion(-11.16030534351145,-31.082089552238806),
        new Posicion(11.16030534351145,-31.6044776119403),
        new Posicion(28.290076335877863,-28.470149253731343),
        new Posicion(-28.290076335877863,-28.470149253731343),
        new Posicion(11.16030534351145,-1.3059701492537314),
        new Posicion(-10.641221374045802,-0.7835820895522387),
        new Posicion(-27.251908396946565,31.6044776119403),
        new Posicion(-10.641221374045802,30.559701492537314),
        new Posicion(9.603053435114505,28.992537313432837),
        new Posicion(25.69465648854962,28.992537313432837)
    };

    Posicion alineacion5[]=new Posicion[]{
        new Posicion(0.2595419847328244,-52.41044776119403),
        new Posicion(-11.16030534351145,-35.78358208955224),
        new Posicion(12.717557251908397,-35.26119402985075),
        new Posicion(28.290076335877863,-28.470149253731343),
        new Posicion(-28.290076335877863,-28.470149253731343),
        new Posicion(14.793893129770993,-18.544776119402986),
        new Posicion(-17.389312977099234,-19.58955223880597),
        new Posicion(-23.618320610687025,-0.7835820895522387),
        new Posicion(5.969465648854961,-5.485074626865671),
        new Posicion(0.2595419847328244,-0.26119402985074625),
        new Posicion(22.580152671755727,-1.3059701492537314)
    };

    Posicion alineacion6[]=new Posicion[]{
        new Posicion(0.2595419847328244,-52.41044776119403),
        new Posicion(-11.16030534351145,-35.78358208955224),
        new Posicion(12.717557251908397,-35.26119402985075),
        new Posicion(28.290076335877863,-28.470149253731343),
        new Posicion(-28.290076335877863,-28.470149253731343),
        new Posicion(14.793893129770993,-18.544776119402986),
        new Posicion(-17.389312977099234,-19.58955223880597),
        new Posicion(-23.618320610687025,-0.7835820895522387),
        new Posicion(6.4885496183206115,-6.529850746268657),
        new Posicion(-6.4885496183206115,-6.529850746268657),
        new Posicion(22.580152671755727,-1.3059701492537314)
    };

    class TacticaDetalleImpl implements TacticaDetalle {

        @Override
        public String getNombre() {
            return "1A";
        }

        @Override
        public String getPais() {
            return "Francia";
        }

        @Override
        public String getEntrenador() {
            return "Omar Masmoudi";
        }

        @Override
        public Color getColorCamiseta() {
            return new Color(0, 0, 255);
        }

        @Override
        public Color getColorPantalon() {
            return new Color(255, 255, 255);
        }

        @Override
        public Color getColorFranja() {
            return new Color(0, 0, 255);
        }

        @Override
        public Color getColorCalcetas() {
            return new Color(255, 255, 255);
        }

        @Override
        public Color getColorPortero() {
            return new Color(0, 0, 0        );
        }

        @Override
        public EstiloUniforme getEstilo() {
            return EstiloUniforme.SIN_ESTILO;
        }

        @Override
        public Color getColorCamiseta2() {
            return new Color(255, 255, 255);
        }

        @Override
        public Color getColorPantalon2() {
            return new Color(255, 255, 255);
        }

        @Override
        public Color getColorFranja2() {
            return new Color(0, 0, 255);
        }

        @Override
        public Color getColorCalcetas2() {
            return new Color(255, 255, 255);
        }

        @Override
        public Color getColorPortero2() {
            return new Color(255, 255, 0);
        }

        @Override
        public EstiloUniforme getEstilo2() {
            return EstiloUniforme.SIN_ESTILO;
        }

        class JugadorImpl implements JugadorDetalle {

            String nombre;
            int numero;
            Color piel, pelo;
            double velocidad, remate, presicion;
            boolean portero;
            Posicion posicion;

            public JugadorImpl(String nombre, int numero, Color piel, Color pelo,
                    double velocidad, double remate, double presicion, boolean portero) {
                this.nombre=nombre;
                this.numero=numero;
                this.piel=piel;
                this.pelo=pelo;
                this.velocidad=velocidad;
                this.remate=remate;
                this.presicion=presicion;
                this.portero=portero;
            }

            @Override
            public String getNombre() {
                return nombre;
            }

            @Override
            public Color getColorPiel() {
                return piel;
            }

            @Override
            public Color getColorPelo() {
                return pelo;
            }

            @Override
            public int getNumero() {
                return numero;
            }

            @Override
            public boolean esPortero() {
                return portero;
            }

            @Override
            public double getVelocidad() {
                return velocidad;
            }

            @Override
            public double getRemate() {
                return remate;
            }

            @Override
            public double getPresicion() {
                return presicion;
            }

        }

        @Override
        public JugadorDetalle[] getJugadores() {
            return new JugadorDetalle[]{
                new JugadorImpl("Jones", 1, new Color(255,200,150), new Color(50,0,0),1d,1d,1d, true),
                new JugadorImpl("Hamon", 2, new Color(255,200,150), new Color(50,0,0),1d,0.625d,0.5d, false),
                new JugadorImpl("Viano", 3, new Color(255,200,150), new Color(50,0,0),1d,0.625d,0.5d, false),
                new JugadorImpl("Weissert", 4, new Color(255,200,150), new Color(50,0,0),1d,0.625d,0.5d, false),
                new JugadorImpl("Chérèque", 5, new Color(255,200,150), new Color(50,0,0),1d,0.625d,0.5d, false),
                new JugadorImpl("Janin", 6, new Color(255,200,150), new Color(50,0,0),1d,0.625d,0.5d, false),
                new JugadorImpl("Lacroix", 7, new Color(255,200,150), new Color(50,0,0),1d,0.625d,0.5d, false),
                new JugadorImpl("Lextrait", 8, new Color(255,200,150), new Color(50,0,0),1d,0.625d,0.5d, false),
                new JugadorImpl("Laburthe", 9, new Color(255,200,150), new Color(50,0,0),1d,1d,1d, false),
                new JugadorImpl("Niewen", 10, new Color(255,200,150), new Color(50,0,0),1d,1d,1d, false),
                new JugadorImpl("Vizzari", 11, new Color(255,200,150), new Color(50,0,0),1d,1d,1d, false)
            };
        }
    }

    TacticaDetalle detalle=new TacticaDetalleImpl();
    @Override
    public TacticaDetalle getDetalle() {
        return detalle;
    }

    @Override
    public Posicion[] getPosicionSaca(SituacionPartido sp) {
    return alineacion5;
    }

    @Override
    public Posicion[] getPosicionRecive(SituacionPartido sp) {
    return alineacion6;
    }


    List<Comando> commands; // list of output commands
    List<Integer> availablePlayers; // Players who can be used for the strategy
    boolean attacking;
    Position[] enemies;
    int enemyPortero;
    Position[] myPlayers;
    List<Integer> dangerousEnemies;
    int[] ableToShot;
    JugadorDetalle[] myPlayersDetails;
    JugadorDetalle[] enemiesDetails;
    int[] myPlayersCantShoot;
    int[] enemiesCantShoot;
    Position ballwithZ, theNextBall;
    double currentDx, currentDy, currentDz;
    Position[] minDestinations, maxDestinations;
    boolean[] defense, middle, attack;
    SituacionPartido sp;
    static Random rand = new Random();
    Position myGoal;

    public Tactica1A() {
        commands = new ArrayList<Comando>();
        availablePlayers = new ArrayList<Integer>();
        dangerousEnemies = new ArrayList<Integer>();
        ballwithZ = new Position(new Posicion());
        theNextBall = new Position(new Posicion());
        minDestinations = new Position[align1.length];
        maxDestinations = new Position[align1.length];
        myPlayers = new Position[align1.length];
        enemies = new Position[align1.length];
        defense = new boolean[align1.length];
        middle = new boolean[align1.length];
        attack = new boolean[align1.length];
        for (int i = 0; i < align1.length; ++i){
            defense[i] = i >= 1 && i <= 4;
            middle[i] = i >= 5 && i <= 7;
            attack[i] = i >= 8;
        }
        minDestinations[0] = align1[0];
        maxDestinations[0] = align1[0];
        enemyPortero = 0;
    }


    public void initRound(SituacionPartido sp) {
        this.sp = sp;
        // Clean the list of output commands
        commands.clear();
        attacking = false;
        Posicion[] temp = sp.rivales();
        for (int i = 0; i < temp.length; ++i){
            enemies[i] = new Position(temp[i]);
        }

        temp = sp.misJugadores();
        for (int i = 0; i < temp.length; ++i){
            myPlayers[i] = new Position(temp[i]);
        }

        Position oldBallwithZ = ballwithZ;
        ballwithZ = new Position(sp.balon());
        ballwithZ.z = sp.alturaBalon();

        dangerousEnemies.clear();
        myGoal = new Position(0, -Constantes.LARGO_CAMPO_JUEGO/2);
        /*
        if (ballwithZ.getY() < -20) {
            // look for the closer to the ball
            double minDist = 1000;
            int minJ = 0;
            for (int j = enemies.length - 1; j >= 0 ; --j){
                if (dangerousEnemies.contains(new Integer(j))) {
                    continue;
                }
                double dist = ballwithZ.distance(enemies[j]);
                if (dist < minDist) {
                    minDist = dist;
                    minJ = j;
                }
            }
            dangerousEnemies.add(new Integer(minJ));
        }*/
        while (enemies.length != dangerousEnemies.size()) {
            double minDist = 1000;
            int minJ = 0;
            for (int j = enemies.length - 1; j >= 0 ; --j){
                if (dangerousEnemies.contains(new Integer(j))) {
                    continue;
                }
                double dist = myGoal.distance(enemies[j]);
                if (dist < minDist) {
                    minDist = dist;
                    minJ = j;
                }
            }
            dangerousEnemies.add(new Integer(minJ));
        }
        ableToShot = sp.puedenRematar();
        myPlayersDetails = sp.detalleMisJugadores();
        enemiesDetails = sp.detalleJugadoresRivales();
        myPlayersCantShoot = sp.iteracionesParaRematar();
        enemiesCantShoot = sp.iteracionesParaRematarRival();

        // Players who can be used for the strategy
        availablePlayers.clear();
        for (int i = 0; i < 11; ++i){
            availablePlayers.add(i);
        }

        for (int i = 0; i < enemiesDetails.length; ++i) {
            if (enemiesDetails[i].esPortero()) {
                enemyPortero = i;
                break;
            }
        }

        //System.out.println(ballwithZ);

        for (int i = 1; i < minDestinations.length; ++i){
            if (attack[i]) {
                /*minDestinations[i] = new Position(minX[i], Math.min(Constantes.LARGO_CAMPO_JUEGO/2 - 10, (Math.max(0, ballwithZ.y))));
                maxDestinations[i] = new Position(maxX[i], (Math.max(0,Math.min(Constantes.LARGO_CAMPO_JUEGO/2 - 10, ballwithZ.y + 30))));*/
                minDestinations[i] = new Position(minX[i], Math.min(Constantes.LARGO_CAMPO_JUEGO/2 - 10, (Math.max(50 - Math.abs(minX[i]) / 2, ballwithZ.y))));
                maxDestinations[i] = new Position(maxX[i], (Math.max(0,Math.min(Constantes.LARGO_CAMPO_JUEGO/2 - 10, ballwithZ.y + 30))));
            } else if (middle[i]) {
                minDestinations[i] = new Position(minX[i], Math.min(Constantes.LARGO_CAMPO_JUEGO/2 - 10, (Math.max(-Constantes.LARGO_CAMPO_JUEGO/2 + 10, ballwithZ.y - 20))));
                maxDestinations[i] = new Position(maxX[i], (Math.max(-Constantes.LARGO_CAMPO_JUEGO/2 + 10,Math.min(Constantes.LARGO_CAMPO_JUEGO/2 - 10, ballwithZ.y + 20))));
            }
            else {
                minDestinations[i] = new Position(align1[i]);
                maxDestinations[i] = new Position(align1[i]);
            }
        }


        /* Calculate Dz, Dx, Dy */
        currentDz = ballwithZ.z - oldBallwithZ.z;
        if (currentDz != 0) {currentDz -= Constantes.G; }
        if (ballwithZ.z == 0 && currentDz < 0) {
            currentDz = (-currentDz - Constantes.G) * Constantes.FACTOR_DISMINUCION_ALTURA_BALON_REBOTE;
            currentDz = roundAtMultiple(currentDz, Constantes.G);
        }

        currentDx = ballwithZ.getX() - oldBallwithZ.getX();
        currentDy = ballwithZ.getY() - oldBallwithZ.getY();

        boolean onGround = (ballwithZ.z == 0 && currentDz >= 0 && currentDz < Constantes.G * 3);
        if (onGround) {
            currentDx *= Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
            currentDy *= Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
        } else {
            currentDx *= Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
            currentDy *= Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
        }
    }

    public class Position {
        double x, y, z;

        public Position() {
            x = y = z = 0;
        }
        public Position(Position p) {
            x = p.x;
            y = p.y;
            z = p.z;
        }
        public Position(Posicion p) {
            x = p.getX();
            y = p.getY();
            z = 0;
        }
        public Position(double x, double y) {
            this.x = x;
            this.y = y;
            this.z = 0;
        }
        public Position(double x, double y, double z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public double getX() {
            return x;
        }

        public double getY() {
            return y;
        }

        public double getZ() {
            return z;
        }

        public double distanceSQ(double px, double py) {
            return (x - px) * (x - px) + (y - py) * (y - py);
        }
        public double distance() {
            return this.distance(new Position());
        }
        public double distance(double px, double py) {
            return Math.sqrt((x - px) * (x - px) + (y - py) * (y - py));
        }
        public double distance(Position p) {
            return Math.sqrt((x - p.x) * (x - p.x) + (y - p.y) * (y - p.y));
        }
        public double distance(Posicion p) {
            return this.distance(new Position(p));
        }
        public Posicion getPosicion() {
            return new Posicion(x,y);
        }
        public Position getDiffPosition(double dx, double dy) {
            return new Position(this.x + dx, this.y + dy);
        }

        private void moveDiff(double dx, double dy) {
            this.x += dx;
            this.y += dy;
        }

        private void moveDiff(double dx, double dy, boolean onGround) {
            if (onGround) {
                this.x += dx;
                this.y += dy;
            } else {
                this.x += dx;
                this.y += dy;
            }
        }

        @Override
        public String toString() {
            return "(" + this.x + ", " + this.y + ", " + this.z + ")";
        }
    }

    public int determineGoal(Position lastBall, Position ball) {
        if (Math.abs(ball.getY()) >= Constantes.LARGO_CAMPO_JUEGO / 2) {
            double posY = 0;
            if (ball.y < 0) {
                posY = -Constantes.LARGO_CAMPO_JUEGO / 2;
            } else {
                posY = Constantes.LARGO_CAMPO_JUEGO / 2;
            }
            double newDy = lastBall.y - ball.y;
            double posZ;
            double posX;

            if (newDy != 0) {
                posZ = ((lastBall.z - ball.z) / newDy) * (posY - ball.y) + ball.z;
            } else {
                posZ = ball.z;
            }
            if (posZ <= Constantes.ALTO_ARCO) {
                if (newDy != 0) {
                    posX = ((lastBall.x - ball.x) / newDy) * (posY - ball.y) + ball.x;
                } else {
                    posX = ball.x;
                }

                //if (Math.abs(posX) < Constantes.LARGO_CAMPO_JUEGO / 2) {

                if (Math.abs(posX) < Constantes.LARGO_ARCO / 2 - Constantes.RADIO_BALON) {
                    if (ball.z - (lastBall.z - ball.z) > Constantes.ALTO_ARCO && ball.getY() > 0) {
                        // Still one turn to determine
                        return 99;
                    }
                    if (ball.getY() < 0) {
                        return -1;
                    } else {
                        return 99;
                    }
                }
            }
        }

        return 0;
    }

    public class DecisionScore {
        boolean forUs;
        int turn;
        int receiver;
        Position finalBall;
        double isGoal;

        int shooter;
        double strength;
        double angle;
        double angleZ;

        Position[] evol;

        private DecisionScore() {}

        public DecisionScore(DecisionScore ds) {
            this.turn = ds.turn;
            this.forUs = ds.forUs;
            this.receiver = ds.receiver;
            this.finalBall = ds.finalBall;
            this.isGoal = ds.isGoal;
            this.shooter = ds.shooter;
            this.strength = ds.strength;
            this.angle = ds.angle;
            this.angleZ = ds.angleZ;
            this.evol = ds.evol.clone();
        }
        public void compute(Position[] ball) {
            compute(ball, false);
        }

        public void compute(Position[] ball, boolean onlyMyPlayers) {

            //evol = ball.clone();
            evol = ball;
            int length = ball.length;

            Mainloop : for (int k = 1; k < length; ++k){
                isGoal = determineGoal(ball[k-1], ball[k]);
                if (isGoal != 0 || isOut(ball[k])) {
                    turn = k;
                    forUs = false;
                    if (isGoal == 99) {
                        if (isReachable(ball[k], enemiesDetails[enemyPortero], enemies[enemyPortero], enemiesCantShoot[enemyPortero], k)) {
                            double dx = ball[k].x - ball[k-1].x;
                            double dy = ball[k].y - ball[k-1].y;
                            double proba = (7d - Math.sqrt(dx * dx + dy * dy)) / 7d;
                            if (rand.nextDouble() > proba) {
                                isGoal = 1 - proba;
                            } else {
                                isGoal = 0;
                            }
                        } else {                            
                            isGoal = 1;
                        }
                    }
                    break Mainloop;
                }

                if (!onlyMyPlayers){
                    for (int j = 0; j < enemies.length; ++j){
                        if (isReachable(ball[k], enemiesDetails[j], enemies[j], enemiesCantShoot[j], k)){
                            turn = k;
                            receiver = j;
                            forUs = false;
                            break Mainloop;
                        }
                    }
                }

                for (int i = 0; i < myPlayers.length; ++i){
                    if (isReachable(ball[k], myPlayersDetails[i], myPlayers[i], myPlayersCantShoot[i], k)){
                        turn = k;
                        receiver = i;
                        forUs = true;
                        break Mainloop;
                    }
                }
            }

            finalBall = ball[turn];

        }

        public void computeSafe(Position[] ball) {

            //evol = ball.clone();
            evol = ball;
            int length = ball.length;

            Mainloop : for (int k = 1; k < length; ++k){
                isGoal = determineGoal(ball[k-1], ball[k]);
                if (isGoal != 0 || isOut(ball[k])) {
                    turn = k;
                    forUs = false;
                    if (isGoal == 99) {
                        if (isReachable(ball[k], enemiesDetails[enemyPortero], enemies[enemyPortero], enemiesCantShoot[enemyPortero], k)) {
                            double dx = ball[k].x - ball[k-1].x;
                            double dy = ball[k].y - ball[k-1].y;
                            double proba = (7d - Math.sqrt(dx * dx + dy * dy)) / 7d;
                            if (rand.nextDouble() > proba) {
                                isGoal = 1 - proba;
                            } else {
                                isGoal = 0;
                            }
                        } else {
                            isGoal = 1;
                        }
                    }
                    break Mainloop;
                }

                for (int i = 0; i < myPlayers.length; ++i){
                    if (defense[i] || myPlayersDetails[i].esPortero()) continue;
                    if (isReachable(ball[k], myPlayersDetails[i], myPlayers[i], myPlayersCantShoot[i], k)){
                        turn = k;
                        receiver = i;
                        forUs = true;
                        break Mainloop;
                    }
                }
            }

            finalBall = ball[turn];

        }

        public boolean isReachable(Position ball, JugadorDetalle jd, Position currentPos, int cantShoot, int turn){
            if (cantShoot <= turn){
                double maxHeight = Constantes.ALTURA_CONTROL_BALON;
                double maxDist = Constantes.DISTANCIA_CONTROL_BALON;
                if (jd.esPortero() && Math.abs(currentPos.getX()) <= Constantes.LARGO_AREA_GRANDE / 2 && Math.abs(currentPos.getY()) > Constantes.ANCHO_AREA_GRANDE){
                    maxDist = Constantes.DISTANCIA_CONTROL_BALON_PORTERO;
                    maxHeight = Constantes.ALTO_ARCO;
                }
                if (ball.z > maxHeight) {
                    return false;
                }
                double dist = ball.distance(currentPos);
                return dist - maxDist <= turn * Constantes.getVelocidad(jd.getVelocidad());
            } else {
                return false;
            }
        }

        public boolean isBetter(DecisionScore ds) {
            if (this.isGoal != ds.isGoal) {
                return this.isGoal > ds.isGoal;
            }

            if (this.forUs != ds.forUs) {
                return this.forUs;
            }

            if (attack[this.receiver] != attack[ds.receiver]) {
                return attack[this.receiver];
            }

            if (isOut(this.finalBall) != isOut(ds.finalBall)){
                return !isOut(this.finalBall);
            }

            double thisDist = this.finalBall.distance(Constantes.centroArcoSup);
            double dsDist = ds.finalBall.distance(Constantes.centroArcoSup);
            if (thisDist != dsDist) {
                return thisDist < dsDist;
            }

            if (this.strength != ds.strength) {
                return this.strength > ds.strength;
            }

            return false;
        }
    }

    public class AverageDecisionScore extends DecisionScore {

        int count;
        double countForUs;
        double countIsGoal;

        public AverageDecisionScore (DecisionScore ds, ArrayList<DecisionScore> dsList, double errorAngle) {
            super(ds);
            /*
            count = 1;
            finalBall = ds.finalBall;
            countIsGoal = ds.isGoal;
            countForUs = ds.forUs?1:0;
            */
            finalBall = new Position();
            count = 0;
            countIsGoal= 0;
            countForUs = 0;
            //System.out.println(errorAngleDeg + " =+= " + errorAngle);
            for (DecisionScore ds2 : dsList) {
                if (Math.abs(ds2.angle - ds.angle) <= errorAngle) {

                    finalBall = new Position((finalBall.getX()*count + ds2.finalBall.getX()) / (count + 1),
                            (finalBall.getY()*count + ds2.finalBall.getY()) / (count + 1), ds.finalBall.z);
                    if (ds2.forUs) {
                        ++countForUs;
                    }
                    if (ds2.isGoal != 0) {
                        countIsGoal += ds2.isGoal;
                    }
                    ++count;
                }
            }
        }

        public boolean isBetter(AverageDecisionScore avgDs) {
            if (countIsGoal/count != avgDs.countIsGoal/avgDs.count) {
                return countIsGoal/count > avgDs.countIsGoal/avgDs.count;
            }

            if (countForUs/count != avgDs.countForUs/avgDs.count) {
                return countForUs/count > avgDs.countForUs/avgDs.count;
            }

            return super.isBetter(avgDs);
        }
    }

    public AverageDecisionScore determineWhereToShot(int player, Position ball) {
        AverageDecisionScore dsMin = null;
        double errorAngleDeg = Constantes.getErrorAngular(myPlayersDetails[player].getPresicion()) * 180 / Math.PI;
        double coefSaque = sp.saco()?0.75:1;

        int oldCannotShot = myPlayersCantShoot[player];
        double dz, oldDz = -1;
        myPlayersCantShoot[player] = Constantes.ITERACIONES_GOLPEAR_BALON + 1;
        for (double strength = 0.1d; strength < 1.01; strength += 0.15d) {
            double speed = strength * Constantes.getVelocidadRemate(myPlayersDetails[player].getRemate()) * coefSaque;
            for (int angleZ = 0; angleZ <= 61; ++angleZ) {
                double radAngleZ = Math.max(0, Math.min(angleZ, Constantes.ANGULO_VERTICAL_MAX)) * (Math.PI / 180d);
                double tempSpeed = speed * Math.cos(radAngleZ);
                dz = roundAtMultiple(tempSpeed * Math.sin(radAngleZ), Constantes.G);
                if (dz == oldDz) {
                    continue;
                }
                oldDz = dz;


                ArrayList<DecisionScore> dsList = new ArrayList<DecisionScore>();
                double anglestep = 3;
                for (double angle = 0; angle < 360; angle+=anglestep) {
                    Position[] evolution = predictShot(player, ball, strength, angle, angleZ);
                    DecisionScore ds = new DecisionScore();
                    ds.compute(evolution);
                    ds.strength = strength;
                    ds.angle = angle;
                    ds.angleZ = angleZ;
                    dsList.add(ds);
                    if (angle > 20 && angle < 160) {
                        anglestep = 2;
                    } else {
                        anglestep = 5;
                    }
                }
                for (DecisionScore ds : dsList) {
                    AverageDecisionScore avgDs = new AverageDecisionScore(ds, dsList, errorAngleDeg);
                    if (dsMin == null || avgDs.isBetter(dsMin)) {
                        dsMin = avgDs;
                    }
                }
            }
        }

        myPlayersCantShoot[player] = oldCannotShot;

        dsMin.shooter = player;
        return dsMin;
    }

    private double roundAtMultiple(double valor, double divisor) {
        return Math.round(valor / divisor) * divisor;
    }

    public static final int MAX_PREVISION = 100;


    public Position[] predictShot(int player, Position ball, double iStrength, double iAngle, double iAngleZ){
        double angle = iAngle * (Math.PI / 180d);
        double speed = iStrength * Constantes.getVelocidadRemate(myPlayersDetails[player].getRemate());
        double angleZ = Math.max(0, Math.min(iAngleZ, Constantes.ANGULO_VERTICAL_MAX)) * (Math.PI / 180d);

        //double angVer = ds.angleZ * Math.PI / 180d;
        //double v0 = ds.strength * Constantes.getVelocidadRemate(getDetalle().getJugadores()[ds.shooter].getRemate());
        AbstractTrayectoria trajectory = new TrayectoriaAire(Math.cos(angleZ) * speed, Math.sin(angleZ) * speed, 0, 0);
        Position newBall = new Position(ball);

        Position[] res = new Position[MAX_PREVISION];
        res[0] = newBall;

        double oldtrX = 1000;
        double oldtrY = 1000;
        for (int step = 1; step < MAX_PREVISION; ++step) {
            double trX = trajectory.getX(step / 60d) * Constantes.AMPLIFICA_VEL_TRAYECTORIA;
            double trY = trajectory.getY(step / 60d) * Constantes.AMPLIFICA_VEL_TRAYECTORIA;
            Position nextBall = new Position(ballwithZ.x + trX * Math.cos(angle), ballwithZ.y + trX * Math.sin(angle), trY*2);
            res[step] = nextBall;
            if (trX == oldtrX && trY == oldtrY) {
                for (int step2 = step; step2 < MAX_PREVISION; ++step2) {
                    res[step2] = nextBall;                    
                }
                break;
            }
            oldtrX = trX;
            oldtrY = trY;            
        }
        return res;
    }

    public boolean isOut(Posicion b){
        double MITAD_ANCHO = Constantes.ANCHO_CAMPO_JUEGO / 2;
        double MITAD_LARGO = Constantes.LARGO_CAMPO_JUEGO / 2;
        return !(b.getX() >= -MITAD_ANCHO && b.getX() <= MITAD_ANCHO &&
                b.getY() >= -MITAD_LARGO && b.getY() <= MITAD_LARGO);
    }

    public boolean isOut(Position b){
        double MITAD_ANCHO = Constantes.ANCHO_CAMPO_JUEGO / 2;
        double MITAD_LARGO = Constantes.LARGO_CAMPO_JUEGO / 2;
        return !(b.getX() >= -MITAD_ANCHO && b.getX() <= MITAD_ANCHO &&
                b.getY() >= -MITAD_LARGO && b.getY() <= MITAD_LARGO);
    }


    public Position[] predictBall(Position ball, double iBallDx, double iBallDy, double iBallDz, int previsions){
        Position[] res = new Position[previsions];
        res[0] = ball;
        double ballDx = iBallDx,  ballDy = iBallDy,  ballDz = iBallDz;

        for (int step = 1; step < previsions; ++step) {
            Position nextBall = new Position(res[step-1]);
            boolean onGround = (nextBall.z == 0 && ballDz >= 0 && ballDz < Constantes.G * 3);
            if (isOut(nextBall)){
                res[step] = res[step-1];
                continue;
            }
            if (onGround) {
                nextBall.moveDiff(ballDx, ballDy);
                ballDx = ballDx * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
                ballDy = ballDy * Constantes.FACTOR_DISMINUCION_VEL_BALON_SUELO;
            } else {
                nextBall.z = roundAtMultiple(nextBall.z + ballDz, Constantes.G);
                nextBall.moveDiff(ballDx, ballDy);
                ballDz = roundAtMultiple(ballDz - Constantes.G, Constantes.G);
                ballDx = ballDx * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
                ballDy = ballDy * Constantes.FACTOR_DISMINUCION_VEL_BALON_AIRE;
                if (nextBall.z == 0) {
                    ballDz = (-ballDz - Constantes.G) * Constantes.FACTOR_DISMINUCION_ALTURA_BALON_REBOTE;
                    ballDz = roundAtMultiple(ballDz, Constantes.G);
                }
            }
            res[step] = nextBall;
        }
        return res;
    }


    public Position[] predictBall(int previsions){
        Position[] res = new Position[previsions];

        for (int step = 0; step < previsions; ++step) {
            double[] nextStep = sp.getTrayectoria(step);
            res[step] = new Position(nextStep[0], nextStep[1], nextStep[2]);
        }
        return res;
    }

    int nbturns = 0;

    @Override
    public List<Comando> ejecutar(SituacionPartido sp) {
        //System.out.println("Turn: " + sp.iteracion());
        // Initialize the round
        initRound(sp);
        ++nbturns;
        //System.out.println((sp.iteracion())  + ": " + ballwithZ.toString());


        if (ableToShot.length > 0) {
            AverageDecisionScore ds = determineWhereToShot(ableToShot[0], ballwithZ);
            if (ds == null) {
                //System.out.println("ds null !!");
            } else {
                
                commands.add(new ComandoGolpearBalon(ds.shooter, ds.angle, ds.strength, ds.angleZ));
                //System.out.println(ds.turn);
                ////////////////////////////

                //System.out.println("isGoal: " + ds.countIsGoal + " - forUs: " + ds.countForUs + " - count: " + ds.count);
                for (int step = 0; step < 10; ++step) {
                    Position pos = ds.evol[step];
                    //System.out.println((step + sp.iteracion())  + "=> " + pos.toString());
                }

                availablePlayers.remove(new Integer(ds.shooter));

                if (ds.forUs) {
                    commands.add(new ComandoIrA(ds.receiver, ds.finalBall.getPosicion()));
                    availablePlayers.remove(new Integer(ds.receiver));
                    //System.out.println(myPlayersDetails[ds.shooter].getNumero() + "->" + myPlayersDetails[ds.receiver].getNumero() + " in " + ds.turn + " turns to (" + ds.finalBall.getX() + "," + ds.finalBall.getY() + ") : proba = " + (ds.countForUs/ds.count));
                } else if (ds.isGoal != 0) {
                    /*System.out.println(myPlayersDetails[ds.shooter].getNumero() + " shoots to score ! : proba = " + (ds.countIsGoal/ds.count));
                    for (int i = 0; i < ds.evol.length ; ++ i){
                     System.out.println("Evol " + i + ": " + ds.evol[i]);
                    }*/
                } else {
                    //System.out.println(myPlayersDetails[ds.shooter].getNumero() + "-> ??? in " + ds.turn + " turns to (" + ds.finalBall.getX() + "," + ds.finalBall.getY() + ") : probaForUs = " + (ds.countForUs/ds.count) + " probaToScore = " + (ds.countIsGoal/ds.count));
                }
            }
        }

        Position[] evolution = predictBall(MAX_PREVISION);
        //Position[] evolution = predictBall(ballwithZ, currentDx, currentDy, currentDz, MAX_PREVISION);
        DecisionScore ds = new DecisionScore();
        ds.compute(evolution);
        theNextBall = evolution[1];
        attacking = ds.forUs || ballwithZ.getY() > 0;



         // The player who will be the nearest to the ball have to go there
        //Position[] evolution = predictBall(ballwithZ, currentDx, currentDy, currentDz, MAX_PREVISION);
        DecisionScore ds2 = new DecisionScore();
        ds2.compute(evolution, true);
        if (ds2.forUs){
            if (ds.forUs || (!myPlayersDetails[ds2.receiver].esPortero() && !defense[ds2.receiver])) {
                commands.add(new ComandoIrA(ds2.receiver, ds2.finalBall.getPosicion()));
                availablePlayers.remove(new Integer(ds2.receiver));
            } else {
                DecisionScore ds3 = new DecisionScore();
                ds3.computeSafe(evolution);
                commands.add(new ComandoIrA(ds3.receiver, ds3.finalBall.getPosicion()));
                availablePlayers.remove(new Integer(ds3.receiver));
            }
        } else if (ds.finalBall.getY() < Constantes.LARGO_ARCO / 2 - Constantes.LARGO_CAMPO_JUEGO / 2 && Math.abs(ds.finalBall.getX()) < Constantes.LARGO_ARCO / 2) {
            commands.add(new ComandoIrA(0, ds2.finalBall.getPosicion()));
            availablePlayers.remove(new Integer(0));
        } 


        for (int j : dangerousEnemies) {
            int iMin = -1;
            double distMin = 0;
            for (int i : availablePlayers) {
                if (i == 0 || (attacking && !defense[i])) continue;
                double dist = enemies[j].distance(myPlayers[i]);
                if (dist < distMin || iMin < 0) {
                    iMin = i;
                    distMin = dist;
                }
            }
            if (iMin < 0) break;

            commands.add(new ComandoIrA(iMin, enemies[j].getPosicion().moverPosicion(0, -Constantes.DISTANCIA_CONTROL_BALON/2)));
            availablePlayers.remove(new Integer(iMin));
        }


        for (int i : availablePlayers) {
            int jMin = -1;
            double distMin = 0;
            for (int j = 0; j < enemies.length; ++j) {
                double dist = enemies[j].distance(myPlayers[i]);
                if (dist < distMin || jMin < 0) {
                    jMin = j;
                    distMin = dist;
                }
            }
            Posicion diff = new Posicion(myPlayers[i].getX() - enemies[jMin].getX(), myPlayers[i].getY() - enemies[jMin].getY());
            double dist = diff.distancia();
            if (dist == 0) {
                diff = new Posicion(0, 1);
            } else {
                diff = new Posicion(diff.getX() / dist, diff.getY() / dist);
            }

            dist = Constantes.getVelocidad(myPlayersDetails[i].getVelocidad());
            Position destination = myPlayers[i].getDiffPosition(diff.getX()*dist, diff.getY()*dist);

            Position tempDest = new Position((minDestinations[i].x + maxDestinations[i].x)/2, (minDestinations[i].y + maxDestinations[i].y)/2);
            dist = destination.distance(tempDest);
            double maxDist = 0;
            for (double x = minDestinations[i].x; x <= maxDestinations[i].x; ++x) {
                for (double y = minDestinations[i].y; y <= maxDestinations[i].y; ++y) {
                    dist = destination.distanceSQ(x,y);
                    if (dist > maxDist) {
                        maxDist = dist;
                        tempDest.x = x;
                        tempDest.y = y;
                    }
                }
            }
            //System.out.println("Destination set for player " + myPlayersDetails[i].getNumero());
            commands.add(new ComandoIrA(i, tempDest.getPosicion()));

        }

        return commands;
    }
}
